package com.sishu.njrtsdms.jooq.generate.generator;

import cn.hutool.core.util.StrUtil;
import com.sishu.njrtsdms.domain.core.ExtendDAOImpl;
import com.sishu.njrtsdms.domain.core.GenericServiceImpl;
import com.sishu.njrtsdms.jooq.generate.strategy.CustomGeneratorStrategy;
import org.jooq.Constants;
import org.jooq.Record;
import org.jooq.codegen.GeneratorStrategy;
import org.jooq.codegen.JavaGenerator;
import org.jooq.codegen.JavaWriter;
import org.jooq.impl.DAOImpl;
import org.jooq.meta.ColumnDefinition;
import org.jooq.meta.TableDefinition;
import org.jooq.meta.UniqueKeyDefinition;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;

import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * 自定义 Java 代码生成器
 *
 * @author Diamond
 */
public class CustomJavaGenerator extends JavaGenerator {
    static final JooqLogger log = JooqLogger.getLogger(CustomJavaGenerator.class);

    /**
     * 继承jooq生成POJOs的entity包名
     * <p>
     * 在此作为响应实体包所以命名为vo
     */
    private static final String ENTITY_PACKAGE_NAME = "vo";

    /**
     * 服务类包名
     */
    private static final String SERVICE_PACKAGE_NAME = "service";

    /**
     * ENTITY不需要后缀标记字符串
     */
    private static final String ENTITY_NO_SUFFIX_FLAG = "NO_SUFFIX";

    /**
     * ENTITY后缀VO字符串
     */
    private static final String ENTITY_SUFFIX_VO_STR = "VO";

    /**
     * 重写了 generateDao， 具体的生成逻辑还是调用父级的方法，只是在生成完成后，获取文件内容，
     * 然后对文件指定的内容进行替换操作
     *
     * @param table
     */
    @Override
    protected void generateDao(TableDefinition table) {
        super.generateDao(table);
        File file = getFile(table, GeneratorStrategy.Mode.DAO);
        if (file.exists()) {
            try {
                String fileContent = new String(FileCopyUtils.copyToByteArray(file));
                String oldExtends = " extends " + DAOImpl.class.getSimpleName();
                String newExtends = " extends " + ExtendDAOImpl.class.getSimpleName();
                fileContent = fileContent.replace("import org.jooq.impl.DAOImpl;\n", "");
                fileContent = fileContent.replace(oldExtends, newExtends);
                FileCopyUtils.copy(fileContent.getBytes(), file);
            } catch (IOException e) {
                log.error("generateDao error: {}", file.getAbsolutePath(), e);
            }
        }
    }

    @Override
    protected void generateDao(TableDefinition table,
                               JavaWriter out) {
        // 用于生成 import com.diamondfsd.jooq.learn.extend.AbstractDAOExtendImpl 内容
        out.ref(ExtendDAOImpl.class);
        // 添加对 Entity 的引用
        out.ref(getStrategy().getFullJavaClassName(table, GeneratorStrategy.Mode.POJO));
        super.generateDao(table, out);
    }

    @Override
    protected void generatePojo(TableDefinition table) {
        super.generatePojo(table);
        // 在生成完POJO后，生成 Entity
        generateVO(table);

        // 生成Service
        generateService(table);
    }

    protected void generateService(TableDefinition table) {
        File serviceFile = getServiceFile(table);
        File serviceImplFile = getServiceImplFile(table);
//        if (serviceFile.exists()) {
//            log.info("Generating Service exists", serviceFile.getName());
//            return;
//        } else {
//            log.info("Generating Service", serviceFile.getName());
//        }
//        if (serviceImplFile.exists()) {
//            log.info("Generating ServiceImpl exists", serviceImplFile.getName());
//            return;
//        } else {
//            log.info("Generating ServiceImpl", serviceImplFile.getName());
//        }
        JavaWriter serviceOw = newJavaWriter(serviceFile);
        JavaWriter serviceImplOw = newJavaWriter(serviceImplFile);
        generateService(table, serviceOw);
        closeJavaWriter(serviceOw);
        generateServiceImpl(table, serviceImplOw);
        closeJavaWriter(serviceImplOw);
    }

    protected void generateServiceImpl(TableDefinition table,
                                       JavaWriter out) {
        UniqueKeyDefinition key = table.getPrimaryKey();
        if (key == null) {
            log.info("Skipping ServiceImpl generation", out.file().getName());
            return;
        }

        String daoFullClassName = getStrategy().getFullJavaClassName(table,
                GeneratorStrategy.Mode.DAO);
        String daoType = out.ref(daoFullClassName);
        String abstractGenericService = out.ref(GenericServiceImpl.class);
        out.ref(Service.class);
        List<ColumnDefinition> keys = key.getKeyColumns();
        String primaryKeyType = getPrimaryKeyType(keys, out);
        String serviceImplClassName = getServiceImplClassName(table);
        String pojoType = out.ref(getStrategy().getFullJavaClassName(table,
                GeneratorStrategy.Mode.POJO));
        String serviceClassName = out.ref(getServiceFullClassName(table));

        out.println("package %s;", getServiceImplTargetPackage());
        out.println();
        out.printImports();
        out.println();
        out.println("@Service");
        out.println("public class %s extends %s<%s, %s> implements %s {",
                serviceImplClassName,
                abstractGenericService,
                pojoType,
                primaryKeyType,
                serviceClassName);
        out.println();
        out.tab(1).println("public %s(%s dao) {", serviceImplClassName, daoType);
        out.tab(2).println("super(dao);");
        out.tab(1).println("}");

        out.print("}");
    }

    protected void generateService(TableDefinition table,
                                   JavaWriter out) {
        UniqueKeyDefinition key = table.getPrimaryKey();
        if (key == null) {
            log.info("Skipping Service generation", out.file().getName());
            return;
        }

        String serviceClassName = getServiceClassName(table);

        out.println("package %s;", getServiceTargetPackage());
        out.println();
        out.printImports();
        out.println();
        out.println("public interface %s {", serviceClassName);
        out.println();
        out.print("}");
    }

    protected String getPrimaryKeyType(List<ColumnDefinition> keyColumns,
                                       JavaWriter out) {
        if (keyColumns.size() == 1) {
            return getJavaType(keyColumns.get(0).getType(resolver(out)), out,
                    GeneratorStrategy.Mode.POJO);
        } else if (keyColumns.size() <= Constants.MAX_ROW_DEGREE) {
            StringBuilder generics = new StringBuilder();
            String separator = "";

            for (ColumnDefinition column : keyColumns) {
                generics.append(separator)
                        .append(out.ref(getJavaType(column.getType(resolver(out)), out,
                                GeneratorStrategy.Mode.POJO)));
                separator = ", ";
            }

            return Record.class.getName() + keyColumns.size() + "<" + generics + ">";
        } else {
            return Record.class.getName();
        }
    }

    /**
     * 生成POJO的子类，生成的子类在jOOQ生成配置目标的父级目录，
     * 所以在每次生成的时候不会被删除，而且在代码内判断了如果文件存在，
     * 则不再创建代码，防止我们手动修改的代码被覆盖
     *
     * @param table
     */
    protected void generateVO(TableDefinition table) {
        File file = getVOFile(table);
        if (file.exists()) {
            log.info("Generating POJO Extend exists", file.getName());
            return;
        } else {
            log.info("Generating POJO Extend", file.getName());
        }
        JavaWriter out = newJavaWriter(file);
        generateVO(table, out);
        closeJavaWriter(out);
    }

    protected void generateVO(TableDefinition table,
                              JavaWriter out) {
        if (table.getPrimaryKey() == null) {
            log.info("PrimaryKey is null, not create pojo extend");
            return;
        }
        String pType = out.ref(getStrategy().getFullJavaClassName(table,
                GeneratorStrategy.Mode.POJO));
        String className = getTableSimpleName(table, ENTITY_SUFFIX_VO_STR);
        out.println("package %s;", getEntityTargetPackage());
        out.println();
        out.printImports();
        out.println();
        out.println("public class %s extends %s {", className, pType);
        out.println();
        out.print("}");
    }

    protected String getProjectTargetDirectory() {
        return "src/main/java";
    }

    protected File getVOFile(TableDefinition table) {
        String dir = getProjectTargetDirectory();
        String pkg = getEntityTargetPackage().replaceAll("\\.", "/");
        return new File(dir + "/" + pkg, getEntityFileName(table));
    }

    protected String getEntityTargetPackage() {
        String targetPackage = getTargetPackage();
        return targetPackage.substring(0, targetPackage.lastIndexOf(".")) + "." + ENTITY_PACKAGE_NAME;
    }

    protected String getEntityFileName(TableDefinition definition) {
        String javaClassName = getTableSimpleName(definition, ENTITY_SUFFIX_VO_STR);
        return javaClassName + ".java";
    }

    /**
     * 将表名转换为驼峰式命名首字母大写,默认不需要后缀
     * s1_user -> S1User
     * hello_world -> HelloWorld
     */
    protected String getTableSimpleName(TableDefinition definition) {
        return getTableSimpleName(definition, ENTITY_NO_SUFFIX_FLAG);
    }

    /**
     * 将表名转换为驼峰式命名首字母大写,并且加上固定后缀
     * s1_user -> S1User
     * hello_world -> HelloWorld
     */
    protected String getTableSimpleName(TableDefinition definition,
                                        String suffix) {
        String tableSimpleNameCamelCase = StringUtils.toCamelCase(definition.getOutputName()
                .replace(' ', '_')
                .replace('-', '_')
                .replace('.', '_'));
        tableSimpleNameCamelCase = StrUtil.replaceIgnoreCase(
                tableSimpleNameCamelCase,
                CustomGeneratorStrategy.TABLE_PREFIX,
                StrUtil.EMPTY);
        if (ENTITY_NO_SUFFIX_FLAG.equals(suffix))
            return tableSimpleNameCamelCase;
        else
            return StrUtil.addSuffixIfNot(tableSimpleNameCamelCase, suffix);
    }

    protected String getEntityFullClassName(TableDefinition definition) {
        return getTargetPackage() + "." + getTableSimpleName(definition,
                ENTITY_NO_SUFFIX_FLAG);
    }

    protected String getServiceFullClassName(TableDefinition definition) {
        return getServiceTargetPackage() + "." + getServiceClassName(definition);
    }

    protected File getServiceFile(TableDefinition table) {
        String dir = getTargetDirectory();
        String pkg = getServiceTargetPackage().replaceAll("\\.", "/");
        return new File(dir + "/" + pkg, getServiceFileName(table));
    }

    protected File getServiceImplFile(TableDefinition table) {
        String dir = getTargetDirectory();
        String pkg = getServiceImplTargetPackage().replaceAll("\\.", "/");
        return new File(dir + "/" + pkg, getServiceImplFileName(table));
    }

    protected String getServiceClassName(TableDefinition definition) {
        return getTableSimpleName(definition) + "Service";
    }

    protected String getServiceImplClassName(TableDefinition definition) {
        return getTableSimpleName(definition) + "ServiceImpl";
    }

    protected String getServiceTargetPackage() {
        return getTargetPackage().substring(0, getTargetPackage().lastIndexOf(".")) + "." +
                SERVICE_PACKAGE_NAME;
    }

    protected String getServiceImplTargetPackage() {
        return getTargetPackage().substring(0, getTargetPackage().lastIndexOf(".")) + "." +
                SERVICE_PACKAGE_NAME + ".impl";
    }

    protected String getServiceFileName(TableDefinition definition) {
        return getServiceClassName(definition) + ".java";
    }

    protected String getServiceImplFileName(TableDefinition definition) {
        return getServiceImplClassName(definition) + ".java";
    }
}
